/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.rafkind.crawl.generator.monsters;

import com.rafkind.crawl.util.Lists;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;

/**
 * RPG Chapter subarea, contains monster battle groups
 *
 * @author drafkind
 */
public class SubArea {

  private static final int GROUP_COUNT = 7;

  private List<MonsterType> monsters;
  private List<Group> groups;

  public SubArea() {
    monsters = new ArrayList<MonsterType>();
    groups = new ArrayList<Group>();
  }

  public List<MonsterType> getMonsters() {
    return monsters;
  }

  public List<Group> getGroups() {
    return groups;
  }

  public void add(MonsterType m) {
    monsters.add(m);
  }

  public void generateGroups(Random random, int targetAttack, int targetDefense, int targetGP, int targetXP) {
    // for each iteration:
    // form the groups
    // assign attacks, defenses
    // see how close the average value is to the targets
    // if not within tolerance, try again

    double error = 0.0;

    List<MonsterType> pool = new ArrayList<MonsterType>();
    for (MonsterType m : monsters)
      pool.add(m);    
    Collections.shuffle(pool, random);

    groups.clear();

    for (int g = 0; g < GROUP_COUNT; g++) {
      int max = (g/2)+1;
      Group group = new Group();

      for (int m = 0; m < max; m++) {
        MonsterType mt = null;
        do {
          mt = pool.remove(random.nextInt(pool.size()/2));
          pool.add(mt);
        } while (group.contains(mt));
        group.add(1, mt);
      }
      groups.add(group);
    }

    // invert the group -> monster map
    Map<MonsterType, List<Group>> m2g = new HashMap<MonsterType, List<Group>>();
    for (Group g : groups) {
      for (MonsterType m : g.getMonsters().keySet()) {
        List<Group> gl = m2g.get(m);
        if (gl == null) {
          gl = new ArrayList<Group>();
          m2g.put(m, gl);
        }
        gl.add(g);
      }
    }

    // add some personalities
    int pCount = 0;
    MonsterType target = monsters.get(random.nextInt(monsters.size()));
    do {      
      if (target.getPersonality() == null) {
        // give em a random personality
        target.setPersonality(MonsterType.Personality.values()[random.nextInt(MonsterType.Personality.values().length)]);
        pCount++;

        // see if we can give a complementary personality to someone else in the same group
        List<Group> participatingGroups = m2g.get(target);
        Group group = participatingGroups.get(random.nextInt(participatingGroups.size()));

        // see if we can find someone in this group that doesn't already have a personality,
        // and assign a complementary one
        if (group != null) {
          List<MonsterType> targets =
            Lists.collect(group.getMonsters().keySet(),
              new Lists.BooleanLambda<MonsterType>() {
                public boolean invoke(MonsterType t) {
                  return true;
                }
              });
          MonsterType target2 = targets.get(random.nextInt(targets.size()));
          if (target2.getPersonality() == null) {
            MonsterType.Personality[] p = MonsterType.Personality.getComplements(target.getPersonality());
            if (p != null && p.length > 0) {
              target2.setPersonality(p[random.nextInt(p.length)]);
              pCount++;
            }
          }
        }
      } else {
        target = monsters.get(random.nextInt(monsters.size()));      
      }
    } while (pCount < 4);

    int numGenes = monsters.size() * (4);
    for (Group g : groups) {
      Map<MonsterType, Integer> m = g.getMonsters();
      numGenes += m.size();
    }

    try {
      Configuration configuration = new DefaultConfiguration();
      configuration.reset();
      configuration.setFitnessFunction(new Fitness(targetAttack, targetDefense, targetGP, targetXP));

      Gene[] sampleGenes = new Gene[numGenes];
      for (int i = 0; i < monsters.size(); i++) {
        sampleGenes[i * 4] = new IntegerGene(configuration, targetAttack/10, targetAttack);
        sampleGenes[i * 4 + 1] = new IntegerGene(configuration, targetDefense/10, targetDefense);
        sampleGenes[i * 4 + 2] = new IntegerGene(configuration, targetGP/10, targetGP);
        sampleGenes[i * 4 + 3] = new IntegerGene(configuration, targetXP/10, targetXP);
      }
      int index = monsters.size() * 4;
      for (Group g : groups) {
        Map<MonsterType, Integer> m = g.getMonsters();
        for (int j = 0; j < m.size(); j++) {
          sampleGenes[index++] = new IntegerGene(configuration, 1, 4);
        }
      }

      Chromosome sampleChromosome = new Chromosome(configuration, sampleGenes);
      configuration.setSampleChromosome(sampleChromosome);;
      configuration.setPopulationSize(500);

      Genotype population = Genotype.randomInitialGenotype(configuration);
      population.evolve();

      double fitness = new Fitness(targetAttack, targetDefense, targetGP, targetXP).evaluate(population.getFittestChromosome());
      
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    /*do {
      
    } while (error > 1.0);*/




  }

  public void print() {
    for (MonsterType mt : monsters) {
      System.out.println("    " + mt.getColor() + mt.getLevel() + " " + mt.getName());
    }
    for (Group g : groups) {
      g.print();
    }
  }

  public class Fitness extends FitnessFunction {
    private int targetAttack;
    private int targetDefense;
    private int targetXP;
    private int targetGP;

    public Fitness(int targetAttack, int targetDefense, int targetGP, int targetXP) {
      this.targetAttack = targetAttack;
      this.targetDefense = targetDefense;
      this.targetGP = targetGP;
      this.targetXP = targetXP;
    }

    @Override
    protected double evaluate(IChromosome ic) {
      double answer = Math.sqrt(targetAttack*targetAttack + targetDefense*targetDefense + targetGP * targetGP + targetXP * targetXP) * groups.size();
      
      int index = 0;
      
      for (MonsterType m : monsters) {
        m.setAtt((Integer) ic.getGene(index++).getAllele());
        m.setDef((Integer) ic.getGene(index++).getAllele());
        m.setGold((Integer) ic.getGene(index++).getAllele());
        m.setXP((Integer) ic.getGene(index++).getAllele());
      }

      for (Group g : groups) {
        int totalAttack = 0;
        int totalDefense = 0;
        int totalGP = 0;
        int totalXP = 0;
        for (Map.Entry<MonsterType, Integer> ev : g.getMonsters().entrySet()) {
          int count = (Integer) ic.getGene(index++).getAllele();
          ev.setValue(count);
          totalAttack += count * ev.getKey().getAtt();
          totalDefense += count * ev.getKey().getDef();
          totalGP += count * ev.getKey().getGold();
          totalXP += count * ev.getKey().getXP();
        }
        
        int attackDiff = totalAttack - targetAttack;
        int defenseDiff = totalDefense - targetDefense;
        int goldDiff = totalGP - targetGP;
        int xpDiff = totalXP - targetXP;

        answer -= Math.sqrt(attackDiff * attackDiff + defenseDiff*defenseDiff + goldDiff * goldDiff + xpDiff * xpDiff);
        if (answer < 0) answer = 0;
      }

      return answer;
    }
  }
}
